package main

import (
	"fmt"
	"jvm/instructions"
	"jvm/instructions/base"
	"jvm/rtda"
	"jvm/rtda/heap"
	. "jvm/typedef"
)

func interpret(method *heap.Method, args []string, logInst bool) {
	thread := rtda.NewThread()
	frame := thread.NewFrame(method)
	thread.PushFrame(frame)
	transferParameters(frame, args)
	defer catchErr(thread)
	run(thread, logInst)
}
func run(thread *rtda.Thread, logInst bool) {
	reader := base.NewCoderReader()
	for {
		frame := thread.CurrentFrame()
		thread.SetPC(frame.NextPC())
		code := frame.Method().Code()
		reader.Reset(code, thread.PC())
		instCode := reader.ReadU1()
		inst := instructions.NewInst(instCode)
		inst.ReadOperands(reader)
		frame.SetNextPC(reader.PC())
		if logInst {
			logInstruction(frame, inst)
		}
		inst.Execute(frame)

		if thread.IsStackEmpty() {
			break
		}
	}

}
func catchErr(thread *rtda.Thread) {
	if r := recover(); r != nil {
		logFrames(thread)
		panic(r)
	}
}

func logFrames(thread *rtda.Thread) {
	for !thread.IsStackEmpty() {
		frame := thread.PopFrame()
		method := frame.Method()
		className := method.Class().Name()
		fmt.Printf(">>pc:%4d %v.%v%v \n",
			frame.NextPC(), className, method.Name(), method.Descriptor())
	}
}

func logInstruction(frame *rtda.Frame, inst base.Inst) {
	method := frame.Method()
	className := method.Class().Name()
	methodName := method.Name()
	fmt.Printf("%v.%v #%2d %T %v\n", className, methodName, frame.Thread().PC(), inst, inst)
}
func transferParameters(frame *rtda.Frame, args []string) {
	loader := frame.Method().Class().Loader()
	arrClass := loader.LoadClass("[Ljava/lang/String;")
	arr := arrClass.NewArray(U4(len(args)))
	refs := arr.Refs()
	for i, _ := range refs {
		jStr := heap.JString(loader, args[i])
		refs[i] = jStr
	}
	frame.LocalVars().SetRef(0, arr)
}
